/*
 * Copyright 2010 Christoph Widulle (christoph.widulle@googlemail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */



package com.android1.amarena2d.nodes;

import com.android1.amarena2d.commons.LeafOrderComperator;
import com.android1.amarena2d.commons.collections.FixedSizeArray;
import com.android1.amarena2d.commons.collections.ReadOnlyCollectionAccess;
import com.android1.amarena2d.engine.Config;
import com.android1.amarena2d.engine.GL;
import com.badlogic.gdx.graphics.Color;

public abstract class BaseNode<T extends LeafEntity> extends BaseLeafEntity implements NodeEntity<T> {

    protected FixedSizeArray<T> children;
    protected boolean renderChildsFirst;

    @Override
    public void init() {
        super.init();
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LeafEntity e = children.get(i);
                    if (e != null) {
                        e.init();
                    }
                }
            }
        }
    }

    public void add(T e) {
        ensureChildren();
        attachChild(e);
        children.sort(false);
    }

    public void add(T... e) {
        ensureChildren();
        for (int i = 0; i < e.length; i++) {
            attachChild(e[i]);
        }
        children.sort(false);
    }

    public void add(T e, int zOrder) {
        e.setZOrder(zOrder);
        add(e);
    }

    @SuppressWarnings("unchecked")
    protected void attachChild(T e) {
        if (e == null)
            throw new IllegalArgumentException("Child must be non-null");
        if (e.getParent() != null)
            throw new IllegalArgumentException("Child has already a parent node.");
        if (!children.add(e)) {
            throw new ArrayStoreException("Node child space exceeded.");
        }
        e.setParent(this);
        if (isPlaying())
            e.enter();
    }

    public void clearChildren(boolean dispose) {

        if (children != null) {
            T child;
            while ((child = children.removeLast()) != null) {
                detachChild(child, dispose);
            }
        }

    }

    public void remove(T e) {
        remove(e, false);
    }

    public void remove(T e, boolean doDispose) {
        detachChild(e, doDispose);
    }

    private void detachChild(T e, boolean doDispose) {
        if (e == null)
            throw new IllegalArgumentException("Child must be non-null");
        if (isPlaying())
            e.exit();
        if (doDispose)
            e.dispose();
        e.setParent(null);
        children.remove(e, true);

    }

    public void reorderChild(T e, int zOrder) {
        if (e == null)
            throw new IllegalArgumentException("Child must be non-null");
        e.setZOrder(zOrder);
        children.sort(true);
    }

    /**
     * Sets the max allowed children you can add to this node.
     * Call it in advance or it will be set the first time you add a child.
     *
     * @param size
     */
    public void ensureChildren(int size) {
        if (children == null) {
            children = new FixedSizeArray<T>(size);
            children.setComparator(new LeafOrderComperator<T>());
        } else if (children.getFreeCount() < size) {
            FixedSizeArray<T> newChildren = new FixedSizeArray<T>(size);
            T child;
            while ((child = children.removeLast()) != null) {
                newChildren.add(child);
            }
            children = newChildren;
            children.setComparator(new LeafOrderComperator<T>());
            children.sort(true);
        }
    }

    /**
     * Will create a list with the size of Config.NODE_MAX_CHILDREN
     */
    public void ensureChildren() {
        if (children == null) {
            ensureChildren(engine.getConfig().get(Config.NODE_MAX_CHILDREN));
        }
    }


    @Override
    public void dispose() {
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LifecycleObject e = children.get(i);
                    if (e != null) {
                        e.dispose();
                    }
                }
            }
        }
        super.dispose();
    }


    @Override
    public void enter() {
        super.enter();
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LifecycleObject e = children.get(i);
                    if (e != null) {
                        e.enter();
                    }
                }
            }
        }
    }


    @Override
    public void exit() {
        active = false;
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LifecycleObject e = children.get(i);
                    if (e != null) {
                        e.exit();
                    }
                }
            }
        }
        super.exit();
    }

    @Override
    public void render() {
        if (!init || !active || !visible)
            return;
        if (Scene.CULLING && !this.disableCulling && !this.isVisibleOnScreen())
            return;

        final float x = getX();
        final float y = getY();

        final boolean isTranslate = transformDelegate != null || x != 0 || y != 0;

        if (isTranslate)
            GL.pushMatrix();

        GL.setColor(this.color);

        if (x != 0 || y != 0)
            GL.translate(x, y);

        if (transformDelegate != null) {
            transformDelegate.apply();
        }

        if (children != null)
            if (renderChildsFirst) {
                renderChilds();
                onRender();
            } else {
                onRender();
                renderChilds();
            }
        else
            onRender();

        onRenderEnd();

        if (isTranslate)
            GL.popMatrix();
    }


    protected void onRender() {

    }

    protected void onRenderEnd() {
    }


    protected void renderChilds() {
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LeafEntity e = children.get(i);
                    if (e != null) {
                        e.render();
                    }
                }
            }
        }
    }

    public void setRenderChildsFirst(boolean renderChildsFirst) {
        this.renderChildsFirst = renderChildsFirst;
    }

    public ReadOnlyCollectionAccess<T> getChildren() {
        return children;
    }

    @Override
    public void setColor(float r, float g, float b) {
        super.setColor(r, g, b);
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LeafEntity e = children.get(i);
                    if (e != null) {
                        e.setColor(r, g, b);
                    }
                }
            }
        }
    }

    @Override
    public void setColor(Color color) {
        super.setColor(color);
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LeafEntity e = children.get(i);
                    if (e != null) {
                        e.setColor(color);
                    }
                }
            }
        }
    }

    @Override
    public void setAlpha(float a) {
        super.setAlpha(a);
        if (children != null) {
            final int size = children.size();
            if (size > 0) {
                for (int i = 0; i < size; i++) {
                    final LeafEntity e = children.get(i);
                    if (e != null) {
                        e.setAlpha(a);
                    }
                }
            }
        }
    }
}
